package cn.uncode.baas.server.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import cn.uncode.dal.cache.CacheManager;
import cn.uncode.dal.core.BaseDAL;
import cn.uncode.dal.criteria.Model;
import cn.uncode.dal.criteria.QueryCriteria;
import cn.uncode.dal.criteria.QueryCriteria.Criteria;
import cn.uncode.dal.descriptor.QueryResult;
import cn.uncode.dal.mongo.MongoDAL;
import cn.uncode.baas.server.cache.SystemCache;
import cn.uncode.baas.server.constant.Resource;
import cn.uncode.baas.server.dto.RestTable;
import cn.uncode.baas.server.internal.context.RestContextManager;
import cn.uncode.baas.server.service.IGenericService;
import cn.uncode.baas.server.utils.AccessTokenUtils;
import cn.uncode.baas.server.utils.DataUtils;
import cn.uncode.baas.server.utils.WatchUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class GenericService implements IGenericService {

    @Autowired
    @Qualifier(value="baseDAL")
    private BaseDAL baseDAL;
    
    @Autowired
    @Qualifier(value="mongoDAL")
    private MongoDAL mongoDAL;

    @Autowired
    private CacheManager cacheManager;

    public QueryResult selectByCriteria(List<String> fields, Map<String, Object> params, String table, int seconds,
            String database) {
    	return selectByCriteria(fields, params, table, seconds, database, false);
    }
    
    public QueryResult selectByCriteria(List<String> fields, Map<String, Object> params, String table, int seconds,
            String database, boolean isNoSql) {
        WatchUtils.lap("generic-service-selectByCriteria-start");
        
        List<String> failFields = checkTableAcl(table, Resource.TABLE_OPTION_READ);
        // query
        QueryCriteria queryCriteria = new QueryCriteria();
        queryCriteria.setDatabase(database);
        queryCriteria.setTable(table);
        Criteria criteria = queryCriteria.createCriteria();
        int pageIndex = Resource.PAGE_INDEX;
        int pageSize = Resource.PAGE_SIZE;
        if (null != params) {
            Iterator<String> iter = params.keySet().iterator();
            while (iter.hasNext()) {
                String name = iter.next();
                Object value = params.get(name);
                if (StringUtils.isNotEmpty(name)) {
                    if ("pageIndex".equals(name)) {
                        pageIndex = Integer.valueOf((String) value);
                    } else if ("pageSize".equals(name)) {
                        pageSize = Integer.valueOf((String) value);
                    } else if ("order".equals(name)) {
                        queryCriteria.setOrderByClause(String.valueOf(value));
                    } else if ("selectOne".equals(name)) {
                        queryCriteria.setSelectOne(Boolean.valueOf(String.valueOf(value)));
                    } else {
                    	if(value instanceof HashMap){
                    		Map<?,?> valueMap = (HashMap<?,?>)value;
                    		Iterator<?> ite = valueMap.keySet().iterator();
                    		while(ite.hasNext()){
                    			String key = String.valueOf(ite.next());
                    			buildCriteria(criteria, name, valueMap, key);
                    		}
                    	}else{
                    		criteria.andColumnEqualTo(name, value);
                    	}
                    }
                }
            }
        }
        queryCriteria.setPageIndex(pageIndex);
        queryCriteria.setPageSize(pageSize);
        WatchUtils.lap("base-dal-selectByCriteria-start");
        QueryResult qresult = null;
        if(isNoSql){
        	qresult = mongoDAL.selectByCriteria(fields, queryCriteria, dalCache(seconds));
        }else{
        	qresult = baseDAL.selectByCriteria(fields, queryCriteria, dalCache(seconds));
        }
        WatchUtils.lap("base-dal-selectByCriteria-end");
        WatchUtils.lap("generic-service-selectByCriteria-end");
        if(failFields != null && failFields.size() > 0){
        	qresult.setResultList(qresult.getList(failFields));
        }
        return qresult;
    }

    public QueryResult selectByPrimaryKey(List<String> fields, Object id, String table, int seconds, String database) {
    	return selectByPrimaryKey(fields, id, table, seconds, database, false);
    }

    public QueryResult selectByPrimaryKey(List<String> fields, Object id, String table, int seconds, String database, boolean isNoSql) {
    	List<String> failFields = checkTableAcl(table, Resource.TABLE_OPTION_READ);
    	Model mod = new Model(database, table);
        mod.setSinglePrimaryKey(id);
        QueryResult qresult = null;
        if(isNoSql){
        	qresult = mongoDAL.selectByPrimaryKey(fields, mod, dalCache(seconds));
        }else{
        	qresult = baseDAL.selectByPrimaryKey(fields, mod, dalCache(seconds));
        }
        if(failFields != null && failFields.size() > 0){
        	qresult.setResultMap(qresult.get(failFields));
        }
        return qresult;
    }

    public QueryResult selectByIds(List<String> fields, Map<String, Object[]> params, String table, int seconds,
            String database) {
    	List<String> failFields = checkTableAcl(table, Resource.TABLE_OPTION_READ);
        // query
        QueryCriteria queryCriteria = new QueryCriteria();
        queryCriteria.setDatabase(database);
        queryCriteria.setTable(table);
        Criteria criteria = queryCriteria.createCriteria();
        if (null != params) {
            Iterator<String> iter = params.keySet().iterator();
            while (iter.hasNext()) {
                String name = iter.next();
                Object[] values = params.get(name);
                if (StringUtils.isNotEmpty(name)) {
                    criteria.andColumnIn(name, Arrays.asList(values));
                }
            }
        }
        QueryResult qresult = baseDAL.selectByCriteria(fields, queryCriteria, dalCache(seconds));
        if(failFields != null && failFields.size() > 0){
        	qresult.setResultList(qresult.getList(failFields));
        }
        return qresult;
    }
    
    public int deleteByPrimaryKey(Object id, String table, String database) {
    	return deleteByPrimaryKey(id, table, database, false);
    }

    public int deleteByPrimaryKey(Object id, String table, String database, boolean isNoSql) {
    	checkTableAcl(table, Resource.TABLE_OPTION_REMOVE);
        Model mod = new Model(database, table);
        mod.setSinglePrimaryKey(id);
        int result = 0;
        if(isNoSql){
        	result = mongoDAL.deleteByPrimaryKey(mod);
        }else{
        	result = baseDAL.deleteByPrimaryKey(mod);
        }
        return result;
    }
    
    @Override
	public int deleteByCriteria(Map<String, Object> params, String table, String database) {
		return deleteByCriteria(params, table, database, false);
	}
    
    public int deleteByCriteria(Map<String, Object> params, String table, String database, boolean isNoSql) {
    	checkTableAcl(table, Resource.TABLE_OPTION_REMOVE);
        QueryCriteria queryCriteria = new QueryCriteria();
        queryCriteria.setDatabase(database);
        queryCriteria.setTable(table);
        Criteria criteria = queryCriteria.createCriteria();
        if (null != params) {
            Iterator<String> iter = params.keySet().iterator();
            while (iter.hasNext()) {
                String name = iter.next();
                Object value = params.get(name);
                if (StringUtils.isNotEmpty(name)) {
                	if(value instanceof HashMap){
                		Map<?,?> valueMap = (HashMap<?,?>)value;
                		Iterator<?> ite = valueMap.keySet().iterator();
                		while(ite.hasNext()){
                			String key = String.valueOf(ite.next());
                			buildCriteria(criteria, name, valueMap, key);
                		}
                	}else{
                		criteria.andColumnEqualTo(name, value);
                	}
                }
            }
        }
        int result = 0;
        if(isNoSql){
        	result = mongoDAL.deleteByCriteria(queryCriteria);
        }else{
        	result = baseDAL.deleteByCriteria(queryCriteria);
        }
        return result;
    }
    
    public int updateByPrimaryKey(Object id, Map<String, Object> params, String table, String database) {
    	return updateByPrimaryKey(id, params, table, database, false);
    }

    public int updateByPrimaryKey(Object id, Map<String, Object> params, String table, String database, boolean isNoSql) {
    	List<String> failFields = checkTableAcl(table, Resource.TABLE_OPTION_UPDATE);
    	params = DataUtils.hiddenRequestField(params, failFields);
        Model mod = new Model(database, table);
        mod.setSinglePrimaryKey(id);
        mod.addContent(params);
        int result = 0;
        if(isNoSql){
        	result = mongoDAL.updateByPrimaryKey(mod);
        }else{
        	result = baseDAL.updateByPrimaryKey(mod);
        }
        return result;
    }
    
    public Object insert(Map<String, Object> params, String table, String database) {
    	return insert(params, table, database, false);
    }

    public Object insert(Map<String, Object> params, String table, String database, boolean isNoSql) {
    	List<String> failFields = checkTableAcl(table, Resource.TABLE_OPTION_INSERT);
    	params = DataUtils.hiddenRequestField(params, failFields);
        Model mod = new Model(database, table);
        mod.addContent(params);
        Object result = 0;
        if(isNoSql){
        	result = mongoDAL.insert(mod);
        }else{
        	result = baseDAL.insert(mod);
        }
        return result;
    }
    
    public int countByCriteria(Map<String, Object> params, String table, int seconds, String database) {
    	return countByCriteria(params, table, seconds, database, false);
    }

    public int countByCriteria(Map<String, Object> params, String table, int seconds, String database, boolean isNoSql) {
    	checkTableAcl(table, Resource.TABLE_OPTION_READ);
        QueryCriteria queryCriteria = new QueryCriteria();
        queryCriteria.setDatabase(database);
        queryCriteria.setTable(table);
        Criteria criteria = queryCriteria.createCriteria();
        if (null != params) {
            Iterator<String> iter = params.keySet().iterator();
            while (iter.hasNext()) {
                String name = iter.next();
                Object value = params.get(name);
                if (StringUtils.isNotEmpty(name)) {
                	if(value instanceof HashMap){
                		Map<?,?> valueMap = (HashMap<?,?>)value;
                		Iterator<?> ite = valueMap.keySet().iterator();
                		while(ite.hasNext()){
                			String key = String.valueOf(ite.next());
                			buildCriteria(criteria, name, valueMap, key);
                		}
                	}else{
                		criteria.andColumnEqualTo(name, value);
                	}
                }
            }
        }
        // paging query
        int total = 0;
        if(isNoSql){
        	total = mongoDAL.countByCriteria(queryCriteria, dalCache(seconds));
        }else{
        	total = baseDAL.countByCriteria(queryCriteria, dalCache(seconds));
        }
        return total;
    }

    public void clearExecuterCache(String bucket, String restName, String option, String version) {
        String key = "rester_" + bucket + "_" + restName + "_" + option + "_" + version + "_executer_";
        if (cacheManager != null) {
            cacheManager.getCache().clear(key);
        }
    }

    public void clearDALCache(String table, String database) {
        if (StringUtils.isNotEmpty(table)) {
            baseDAL.clearCache(database, table);
        }
    }
    
    /**
     * check table acl
     * @param table
     * @param option
     * @return
     */
    private List<String> checkTableAcl(String table, String option) {
		String bucket = RestContextManager.getContext().getBucket();
        List<String> failFields = null;
        if(StringUtils.isNotEmpty(bucket)){
        	RestTable restTable = SystemCache.getRestTable(bucket, table);
        	failFields = AccessTokenUtils.checkPermissions(restTable, option);
        }
		return failFields;
	}

    private int dalCache(int seconds) {
        return seconds == BaseDAL.NO_CACHE ? BaseDAL.NO_CACHE : 0;
    }
    
    private Object convertValue(Object value){
    	String vstr = String.valueOf(value);
    	if(vstr.endsWith(".0")){
    		vstr = vstr.replace(".0", "");
    		return Integer.valueOf(vstr);
    	}
    	return value;
    }
    
    /**
     * 
     * @param criteria
     * @param name
     * @param valueMap
     * @param key
     */
    private void buildCriteria(Criteria criteria, String name, Map<?, ?> valueMap, String key) {
		if(Condition.IS_NULL.equals(key)){
			criteria.andColumnIsNull(name);
		}else if(Condition.IS_NOT_NULL.equals(key)){
			criteria.andColumnIsNotNull(name);
		}else if(Condition.EQUAL.equals(key)){
			criteria.andColumnEqualTo(name, convertValue(valueMap.get(key)));
		}else if(Condition.NOT_EQUAL.equals(key)){
			criteria.andColumnNotEqualTo(name, convertValue(valueMap.get(key)));
		}else if(Condition.GREATER_THAN.equals(key)){
			criteria.andColumnGreaterThan(name, convertValue(valueMap.get(key)));
		}else if(Condition.GREATER_THAN_OR_EQUAL.equals(key)){
			criteria.andColumnGreaterThanOrEqualTo(name, convertValue(valueMap.get(key)));
		}else if(Condition.LESS_THAN.equals(key)){
			criteria.andColumnLessThan(name, convertValue(valueMap.get(key)));
		}else if(Condition.LESS_THAN_OR_EQUAL.equals(key)){
			criteria.andColumnGreaterThanOrEqualTo(name, convertValue(valueMap.get(key)));
		}else if(Condition.LIKE.equals(key)){
			criteria.andColumnLike(name, convertValue(valueMap.get(key)));
		}else if(Condition.NOT_LIKE.equals(key)){
			criteria.andColumnNotLike(name, convertValue(valueMap.get(key)));
		}else if(Condition.IN.equals(key)){
			String vlist = String.valueOf(valueMap.get(key));
			List<Object> array = new ArrayList<Object>();
			array.addAll(Arrays.asList(vlist.split(",")));
			criteria.andColumnIn(name, array);
		}else if(Condition.NOT_IN.equals(key)){
			String vlist = String.valueOf(valueMap.get(key));
			List<Object> array = new ArrayList<Object>();
			array.addAll(Arrays.asList(vlist.split(",")));
			criteria.andColumnNotIn(name, array);
		}else if(Condition.BETWEEN.equals(key)){
			String valbt = String.valueOf(valueMap.get(key));
			String[] val = valbt.split(",");
			criteria.andColumnBetween(name, val[0], val[1]);
		}else if(Condition.NOT_BETWEEN.equals(key)){
			String valbt = String.valueOf(valueMap.get(key));
			String[] val = valbt.split(",");
			criteria.andColumnBetween(name, val[0], val[1]);
		}
	}
    
    
    public static class Condition{
    	public static final String TAG= "$";
        public static final String IS_NULL = "$nu";
        public static final String IS_NOT_NULL = "$nnu";
        public static final String EQUAL = "$eq";
        public static final String NOT_EQUAL = "$neq";
        public static final String GREATER_THAN = "$gt";
        public static final String GREATER_THAN_OR_EQUAL = "$gte";
        public static final String LESS_THAN = "$lt";
        public static final String LESS_THAN_OR_EQUAL = "$lte";
        public static final String LIKE = "$lk";
        public static final String NOT_LIKE = "$nlk";
        public static final String IN = "$in";
        public static final String NOT_IN = "$nin";
        public static final String BETWEEN = "$bt";
        public static final String NOT_BETWEEN = "$nbt";
       
    }



	

}
